import numpy as np
from scipy.sparse import csr_matrix


def sparse(i, j, v, m=None, n=None):
    """
    Create and compressing a matrix that have many zeros
    Parameters:
        i: 1-D array representing the index 1 values
            Size n1
        j: 1-D array representing the index 2 values
            Size n1
        v: 1-D array representing the values
            Size n1
        m: integer representing x size of the matrix >= n1
        n: integer representing y size of the matrix >= n1
    Returns:
        s: 2-D array
            Matrix full of zeros excepting values v at indexes i, j
    """
    if m is None or n is None:
        return csr_matrix((v, (i, j)))
    else:
        return csr_matrix((v, (i, j)), shape=(m, n))


def vector_idx_minus_1_from_matlab(mat):
    if np.min(mat) != 0:
        # For MATLAB
        vec_minus_1 = mat - 1
        return vec_minus_1
    return mat


def normalize(data):
    if np.max(data) == np.min(data):
        return data
    return (data - np.min(data)) / (np.max(data) - np.min(data))


def normalize_old(v):
    norm = np.linalg.norm(v)
    if norm == 0:
        return v
    return v / norm


def makeweights(edges, vals, valScale, points=None, geomScale=0, EPSILON=1e-5):
    # Compute intensity differences
    if valScale > 0:
        edges_0 = vector_idx_minus_1_from_matlab(edges)
        valDistances = np.sqrt(
            np.sum((vals[edges_0[:, 0], :] - vals[edges_0[:, 1], :]) ** 2, axis=1)
        )
        valDistances = normalize(valDistances)  # Normalize to [0,1]
    else:
        valDistances = np.zeros((edges.shape[0], 1))
        valScale = 0

    # Compute geomDistances, if desired
    if points is not None and geomScale != 0:
        edges_0 = vector_idx_minus_1_from_matlab(edges)
        geomDistances = np.sqrt(
            np.abs(
                np.sum(
                    (points[edges_0[:, 0], :] - points[edges_0[:, 1], :]) ** 2, axis=1
                )
            )
        )
        geomDistances = normalize(geomDistances)  # Normalize to [0,1]
    else:
        geomDistances = np.zeros(edges.shape[0])
        geomScale = 0

    # Compute Gaussian weights
    weights = np.exp(-(geomScale * geomDistances + valScale * valDistances)) + EPSILON

    return weights


def prune_knn(W, nb):
    # W - affinity matrix
    # nb - the number of nearest neighbors
    # A - symmetric (weighted) knn graph

    n = W.shape[0]
    W_T = W.transpose(copy=True)
    idx = []
    for i in range(n):
        tmp_row = W_T.getrow(i).toarray()[0].ravel()
        idx.append(tmp_row.argsort()[::-1])
    idx = np.asarray(idx)
    idx = idx.transpose()

    idxR = idx[:nb, :]
    idxC = np.ones((nb, 1), dtype=np.int32) * np.asarray(list(range(n)))
    if not len(idxR.flatten()) == len(idxC.flatten()):
        raise ValueError(
            "The length of flattened idxR should be equal to the length of flattened idxC."
        )
    data = np.asarray([1] * len(idxR.flatten()))
    A = sparse(idxR.flatten(), idxC.flatten(), data, n, n)
    A_T = A.transpose(copy=True)

    A_OR = (A + A_T).astype(np.bool).astype(np.int32)
    A = A_OR.multiply(W)

    return A


def adjacency(edges, weights=None, N=None):
    # Function W=adjacency(edges,weights,N) computes the weighted
    #    adjacency matrix of a point and edge set.  For an unweighted
    #    matrix, set weights equal to ones(1,M) where M is the number
    #    of edges in the graph.
    #
    # Inputs:    edges - A Mx2 list of M edges indexing into points
    #            weights - The weights used to determine matrix values.
    #                If not specified, uses vector of all ones
    #            N - Optional number of nodes in the graph.  Used if
    #                N > max(edges) (i.e., isolated nodes are present)
    #
    # Outputs:   W - Adjacency matrix
    #
    #
    # Note: Adjacency matrix is not of the Seidel type
    #    (i.e. all values are positive)
    # 5/19/03 - Leo Grady
    #
    #  Copyright (C) 2002, 2003 Leo Grady <lgrady@cns.bu.edu>
    #    Computer Vision and Computational Neuroscience Lab
    #    Department of Cognitive and Neural Systems
    #    Boston University
    #    Boston, MA  02215
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software
    #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    #
    #  Date - $Id: adjacency.m,v 1.2 2003/08/21 17:29:29 lgrady Exp $
    # ========================================================================%

    # If weights are not specified, use unity weighting
    if weights is None:
        weights = np.ones(edges.shape[0], 1)

    # If N is not specified, use maximum values of edges
    if N is None:
        N = np.max(edges)

    # Build sparse adjacency matrix
    edges_0 = vector_idx_minus_1_from_matlab(edges)
    edges_0_1_idxes = np.append(edges_0[:, 0], edges_0[:, 1], 0)
    edges_1_0_idxes = np.append(edges_0[:, 1], edges_0[:, 0], 0)
    weights_append = np.append(weights, weights, 0)
    W = sparse(edges_0_1_idxes, edges_1_0_idxes, weights_append, N, N)

    # print(W)
    return W
